-module(tcp_proxy).
-compile(export_all).
-behaviour(gen_server).

-define(MSGPORT, 8000).
-define(MAX_CONNECTS, 3).
-define(TCP_OPTIONS,[list, {packet, 0}, {active, false}, {reuseaddr, true}]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

-record(tp_state,
        {tcp_opts = ?TCP_OPTIONS,  % TCP Socket options
         port,                     % Port connections accepted on
         listen,                   % The listen socket instance
         accept_pid,               % The current accept Process Id
         accept_deaths = 0,        % The number of times accept EXITed
         clients,                  % ETS table of currently active clients
         client_count = 0,         % Current number of active requests
         max_active = 0,           % Maximum number of active allowed
         requests = 0,             % Total number of requests handled
         server_busy = 0,          % Number of times server was busy
         pmodule,                  % ProxyModule name
         pm_state                  % ProxyModule's dynamic state
        }).

start_link(ProxyModule) ->
    start_link(ProxyModule, ProxyModule, ?MSGPORT, ?MAX_CONNECTS, ?CLIENT_TAB);

start_link(ServerName, ProxyModule, Port, MaxConnects, ClientTab) ->
    case gen_server:start_link({local, ServerName}, ?MODULE, [ProxyModule, Port, MaxConnects, ClientTab], []) of
        {ok, Pid} ->
            start_listen(Pid, Port);
        {error, {already_started, OldPid}}_->
            {ok, OldPid};
        Error ->
            error_logger:error_report([{start_link, Error}])
    end.

report_clients(Server) ->
    gen_server:call(Server, report_clients).

handle_request(Server, Request, Data) ->
    gen_server:call(Server, {msg, Request, Data}).

stop(Server) ->
    gen_server:call(Server, stop).

init([ProxyModule, Port, MaxConnects, ClientTab]) ->

    % Let ProxyModule initialize, then setup State and return.
    case catch ProxyModule:init() of
        {ok, ProxyState} ->
            process_flag(trap_exit, true),
            {ok, #tp_state{port = Port, pmodule = ProxyModule,
                           pm_state = ProxyState, max_active = MaxConnects,
                           clients = ets:new(ClientTab, [named_table])}};
        Error ->
            {stop, Error}
    end.
    
start_listen(Pid, Port) ->
    case gen_server:call(Pid, {listen, Port}) of
        ok ->
            start_accept(Pid);	
        Error ->
            error_logger:error_report([{start_listen, Error}]),
            Error
    end.
     
start_accept (Pid) when pid(Pid) ->
    case gen_server:call(Pid, {accept, Pid}) of
        ok ->
            {ok, Pid};
        Other ->
            error_logger:error_report([{start_accept, Other}]),
            Other
    end.
    
% Set up a new listen socket to prepare for connections
handle_call({listen, NewPort}, _From, #tp_state{port = OldPort,
                                                tcp_opts = TcpOpts} = State) ->
    % Open a new listen port
    case catch gen_tcp:listen(NewPort, TcpOpts) of

	% If we succeed, modify the state (possibly closing the old
	% listen port) and return a successful reply
	{ok, LSocket} ->
	    NewPortState = State#tp_state{port = NewPort, listen = LSocket},
	    case OldPort of
		undefined ->
		    {reply, ok, NewPortState};
		NewPort ->
		    {reply, ok, NewPortState};

		% Close the old port, but don't pay attention to result
		Other ->
		    Table = State#tp_state.clients,
		    ets:delete_all_objects(Table),
		    gen_tcp:close(OldPort),
		    {reply, ok, NewPortState#tp_state{client_count = 0}}
	    end;

	% If the call fails, tell the caller and don't change state or
	% close the existing listen port since terminate/2 will finish
	NotOK ->
	    {stop, NotOK, State}
    end;
	
% Accept connections in a new process, recording it for later reference
handle_call({accept, Server}, _From, #tp_state{accept_pid = undefined,
                                               listen = LSocket} = State) ->
    Pid = spawn_link(fun() -> accept(Server, LSocket) end),
    {reply, ok, State#tp_state{accept_pid = Pid}};
	
	